package com.pojo2json;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.gson.GsonBuilder;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationGroup;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications.Bus;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NonNls;

public class MyPoJo2JsonAction extends AnAction {

    private static final NotificationGroup notificationGroup = NotificationGroup.balloonGroup("pojo2json.NotificationGroup");
    @NonNls
    private static final Map<String, Object> normalTypes = new HashMap();
    private static final GsonBuilder gsonBuilder = (new GsonBuilder()).setPrettyPrinting();
    private static final BigDecimal zero;

    public MyPoJo2JsonAction() {
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
        Editor editor = (Editor) e.getData(CommonDataKeys.EDITOR);
        PsiFile psiFile = (PsiFile) e.getData(CommonDataKeys.PSI_FILE);
        Project project = e.getProject();
        PsiElement elementAt = psiFile.findElementAt(editor.getCaretModel().getOffset());
        PsiClass selectedClass = (PsiClass) PsiTreeUtil.getContextOfType(elementAt, new Class[]{PsiClass.class});

        Notification error;
        try {
            Map<String, Object> kv = getFields(selectedClass);
            String json = gsonBuilder.create().toJson(kv);
            StringSelection selection = new StringSelection(json);
            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            clipboard.setContents(selection, selection);
            String message = "Convert " + selectedClass.getName() + " to JSON success, copied to clipboard.";
            Notification success = notificationGroup.createNotification(message, NotificationType.INFORMATION);
            Bus.notify(success, project);
        } catch (KnownException var13) {
            error = notificationGroup.createNotification(var13.getMessage(), NotificationType.WARNING);
            Bus.notify(error, project);
        } catch (Exception var14) {
            error = notificationGroup.createNotification("Convert to JSON failed.", NotificationType.ERROR);
            Bus.notify(error, project);
        }
    }

    private static Map<String, Object> getFields(PsiClass psiClass) {
        Map<String, Object> map = new LinkedHashMap();
        if (psiClass == null) {
            return map;
        } else {
            PsiField[] var2 = psiClass.getAllFields();
            int var3 = var2.length;

            for (int var4 = 0; var4 < var3; ++var4) {
                PsiField field = var2[var4];
                PsiAnnotation annotation = field.getAnnotation("com.fasterxml.jackson.annotation.JsonProperty");
                String name = null == annotation ? field.getName() : annotation.findAttributeValue("value").getText().replaceAll("\"", "");
                map.put(name, typeResolve(field.getType(), 0));
            }

            return map;
        }
    }

    private static Object typeResolve(PsiType type, int level) {
        ++level;
        level = level;
        if (type instanceof PsiPrimitiveType) {
            return getDefaultValue(type);
        } else if (type instanceof PsiArrayType) {
            List<Object> list = new ArrayList();
            PsiType deepType = type.getDeepComponentType();
            list.add(typeResolve(deepType, level));
            return list;
        } else {
            Map<String, Object> map = new LinkedHashMap();
            PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type);
            if (psiClass == null) {
                return map;
            } else if (psiClass.isEnum()) {
                PsiField[] var13 = psiClass.getFields();
                int var14 = var13.length;

                for (int var15 = 0; var15 < var14; ++var15) {
                    PsiField field = var13[var15];
                    if (field instanceof PsiEnumConstant) {
                        return field.getName();
                    }
                }

                return "";
            } else {
                List<String> fieldTypeNames = new ArrayList();
                PsiType[] types = type.getSuperTypes();
                fieldTypeNames.add(type.getPresentableText());
                fieldTypeNames.addAll((Collection) Arrays.stream(types).map(PsiType::getPresentableText).collect(Collectors.toList()));
                ArrayList retain;
                if (fieldTypeNames.stream().anyMatch((s) -> {
                    return s.startsWith("Collection") || s.startsWith("Iterable");
                })) {
                    retain = new ArrayList();
                    PsiType deepType = PsiUtil.extractIterableTypeParameter(type, false);
                    retain.add(typeResolve(deepType, level));
                    return retain;
                } else {
                    retain = new ArrayList(fieldTypeNames);
                    retain.retainAll(normalTypes.keySet());
                    if (!retain.isEmpty()) {
                        return normalTypes.get(retain.get(0));
                    } else if (level > 500) {
                        throw new KnownException("This class reference level exceeds maximum limit or has nested references!");
                    } else {
                        PsiField[] var7 = psiClass.getAllFields();
                        int var8 = var7.length;

                        for (int var9 = 0; var9 < var8; ++var9) {
                            PsiField field = var7[var9];
                            map.put(field.getName(), typeResolve(field.getType(), level));
                        }

                        return map;
                    }
                }
            }
        }
    }

    public static Object getDefaultValue(PsiType type) {
        if (!(type instanceof PsiPrimitiveType)) {
            return null;
        } else {
            String var1 = type.getCanonicalText();
            byte var2 = -1;
            switch (var1.hashCode()) {
                case -1325958191:
                    if (var1.equals("double")) {
                        var2 = 7;
                    }
                    break;
                case 104431:
                    if (var1.equals("int")) {
                        var2 = 4;
                    }
                    break;
                case 3039496:
                    if (var1.equals("byte")) {
                        var2 = 1;
                    }
                    break;
                case 3052374:
                    if (var1.equals("char")) {
                        var2 = 2;
                    }
                    break;
                case 3327612:
                    if (var1.equals("long")) {
                        var2 = 5;
                    }
                    break;
                case 64711720:
                    if (var1.equals("boolean")) {
                        var2 = 0;
                    }
                    break;
                case 97526364:
                    if (var1.equals("float")) {
                        var2 = 6;
                    }
                    break;
                case 109413500:
                    if (var1.equals("short")) {
                        var2 = 3;
                    }
            }

            switch (var2) {
                case 0:
                    return false;
                case 1:
                    return 0;
                case 2:
                    return '\u0000';
                case 3:
                    return Short.valueOf((short) 0);
                case 4:
                    return 0;
                case 5:
                    return 0L;
                case 6:
                    return zero;
                case 7:
                    return zero;
                default:
                    return null;
            }
        }
    }

    static {
        zero = BigDecimal.ZERO.setScale(2, RoundingMode.UNNECESSARY);
        LocalDateTime now = LocalDateTime.now();
        String dateTime = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        normalTypes.put("Boolean", false);
        normalTypes.put("Float", zero);
        normalTypes.put("Double", zero);
        normalTypes.put("BigDecimal", zero);
        normalTypes.put("Number", 0);
        normalTypes.put("CharSequence", "");
        normalTypes.put("Date", dateTime);
        normalTypes.put("Temporal", now.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        normalTypes.put("LocalDateTime", dateTime);
        normalTypes.put("LocalDate", now.toLocalDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        normalTypes.put("LocalTime", now.toLocalTime().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
    }
}
